package convert

// 进制转换
// TODO：字符池未作字符重复检查

import (
	"errors"
	"gitee.com/big_meteor/godash/arr"
	"gitee.com/big_meteor/godash/number"
	"math"
	"strconv"
	"strings"
	"unicode/utf8"
)

var Char = []string{
	"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
	"a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
	"k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
	"u", "v", "w", "x", "y", "z", "A", "B", "C", "D",
	"E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
	"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X",
	"Y", "Z",
}

const (
	Sep     = "_"
	NumFlag = "#"
)

type Convert struct {
	Sep     string   // 分隔符 不能出现在字符池内
	Char    []string // 字符池 len(char) > 2
	Radix   int64    // 进制基数 min = 2; max = len(char) - 1
	NumFlag string   // 数字标识符 不能出现在字符池内
}

// 前置检查
func (this *Convert) before() error {
	char := this.Char
	radix := this.Radix

	if nil == char {
		char = Char
	} else if 2 > len(char) {
		return errors.New("the minimum length of char pool is 2")
	}

	if 0 == radix {
		radix = int64(len(char))
	} else if 2 > radix {
		return errors.New("radix cannot be less than 2")
	} else if radix > int64(len(char)) {
		return errors.New("radix cannot be greater than char pool length")
	}

	this.Char = char
	this.Radix = radix
	return nil
}

// 字符串前置检查
func (this *Convert) beforeString() error {
	err := this.before()
	if nil != err {
		return err
	}

	sep := this.Sep
	numFlag := this.NumFlag

	if "" == sep {
		sep = Sep
	}

	if "" == numFlag {
		numFlag = NumFlag
	}

	if this.IsInChar(sep) {
		return errors.New("a separator cannot be in a char pool")
	}

	if this.IsInChar(numFlag) {
		return errors.New("a number flag cannot be in a char pool")
	}

	this.Sep = sep
	this.NumFlag = numFlag
	return nil
}

// 检查是否在字符池中
func (this Convert) IsInChar(c string) bool {
	result, _ := arr.IndexOf(this.Char, c)
	return result
}

// 数字编码
func (this *Convert) Encode(n int64) (string, error) {
	err := this.before()
	if nil != err {
		return "", err
	}

	char := this.Char
	radix := this.Radix

	result := ""
	for n != 0 {
		result = char[n%int64(len(char))] + result
		n = n / radix
	}

	return result, nil
}

// 数字解码
func (this *Convert) Decode(str string) (int64, error) {
	err := this.before()
	if nil != err {
		return 0, err
	}

	if "" == str {
		return 0, nil
	}

	var result float64
	slice := strings.Split(str, "")

	for k, v := range slice {
		flag, index := arr.IndexOf(this.Char, v)
		if flag {
			x := float64(this.Radix)
			y := float64(len(slice) - 1 - k)
			result += float64(index) * math.Pow(x, y)
		} else {
			return 0, errors.New("\"" + v + "\" not in character pool")
		}
	}

	return int64(result), nil
}

// 字符串编码
func (this *Convert) EncodeString(str string) (string, error) {
	err := this.beforeString()
	if nil != err {
		return "", err
	}

	if "" == str {
		return "", nil
	}

	length := utf8.RuneCountInString(str)
	if 2 > length || number.IsNum(str) {
		return this.EncodeStringSingle(str)
	}

	slice := strings.Split(str, "")

	sliceNew := []string{slice[0]}
	for i := 1; i < len(slice); i++ {
		v := slice[i]
		lastIndex := len(sliceNew) - 1
		if number.IsNum(v) && number.IsNum(sliceNew[lastIndex]) && 10 > len(sliceNew[lastIndex]) {
			sliceNew[lastIndex] += v
		} else {
			sliceNew = append(sliceNew, v)
		}
	}

	var result = make([]string, len(sliceNew))

	for i := 0; i < len(sliceNew); i++ {
		v := sliceNew[i]

		val, err := this.EncodeStringSingle(v)
		if nil != err {
			return "", err
		}

		result[i] = val
	}

	return strings.Join(result, this.Sep), nil
}

// 字符串解码
func (this *Convert) DecodeString(str string) (string, error) {
	err := this.beforeString()
	if nil != err {
		return "", err
	}

	if "" == str {
		return "", errors.New("decode string cannot be empty")
	}

	slice := strings.Split(str, this.Sep)

	var result string
	for i := 0; i < len(slice); i++ {
		v := slice[i]

		if this.NumFlag == v[0:1] {
			dv, err := this.Decode(v[1:])
			if nil != err {
				return "", err
			}
			result += strconv.FormatInt(dv, 10)
		} else {
			dv, err := this.Decode(v)
			if nil != err {
				return "", err
			}
			result += string(rune(dv))
		}
	}

	return result, nil
}

// 单个字符编码
// 包含单个字符、数字和多位数字
func (this *Convert) EncodeStringSingle(str string) (string, error) {
	err := this.beforeString()
	if nil != err {
		return "", err
	}

	if number.IsNum(str) {
		num, err := strconv.ParseInt(str, 10, 64)
		if nil != err {
			return "", err
		}

		result, err := this.Encode(num)
		if nil != err {
			return "", err
		}
		return this.NumFlag + result, nil
	}

	if "" == str {
		return "", nil
	}

	length := utf8.RuneCountInString(str)
	if 1 < length {
		return "", errors.New("only one character is supported")
	}

	r, _ := utf8.DecodeRuneInString(str)
	return this.Encode(int64(r))
}
